package com.example.socket.filter.session;

import com.example.socket.anno.InBody;
import com.example.socket.anno.InSession;
import com.example.socket.anno.SocketCommand;
import com.example.socket.anno.SocketModule;
import com.example.socket.core.Command;
import com.example.socket.core.Request;
import com.example.socket.core.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ProxyInvokerSupport<T> {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    /** 方法对应指令缓存对象 */
    protected static final ConcurrentHashMap<Method, InvokerMeta> caches = new ConcurrentHashMap<>();

    /**
     * 构建请求　
     * @param method
     * @param args
     * @return
     */
    protected Request<Object> buildRequest(Method method, Object[] args) {
        InvokerMeta info = caches.get(method);
        if (info == null) {
            Class<?> declaringClass = method.getDeclaringClass();
            SocketModule socketModule = declaringClass.getAnnotation(SocketModule.class);
            short module = socketModule.value();
            SocketCommand socketCommand = method.getAnnotation(SocketCommand.class);
            // 创建指令对象
            Command command = Command.valueOf(module,socketCommand.value());

            Map<Integer, ParamMeta> params = new HashMap<>();
            Annotation[][] paramAnnotations = method.getParameterAnnotations();
            Class<?>[] paramTypes = method.getParameterTypes();
            if (paramTypes.length == 1) {
                Class<?> type = paramTypes[0];
                if (!type.isAssignableFrom(Session.class)) {
                    boolean inrequest = true;
                    for (Annotation a : paramAnnotations[0]) {
                        if (a instanceof InSession) {
                            inrequest = false;
                            break;
                        }
                        if (a instanceof InBody) {
                            String name = ((InBody) a).value();
                            params.put(0, ParamMeta.valueOf(name, type));
                            inrequest = false;
                            break;
                        }
                    }
                    if (inrequest) {
                        params.put(0, ParamMeta.valueOf(null, type));
                    }
                }
            } else if (paramTypes.length > 1) {
                for (int index = 0; index < paramAnnotations.length; index++) {
                    Class<?> type = paramTypes[index];
                    if (type.isAssignableFrom(Session.class)) {
                        continue;
                    }
                    boolean inrequest = true;
                    for (Annotation a : paramAnnotations[index]) {
                        if (a instanceof InSession) {
                            inrequest = false;
                            break;
                        }
                        if (a instanceof InBody) {
                            String name = ((InBody) a).value();
                            params.put(index, ParamMeta.valueOf(name, type));
                            inrequest = false;
                            break;
                        }
                    }
                    if (inrequest) {
                        params.put(index, ParamMeta.valueOf(null, type));
                    }
                }
            }
            info = InvokerMeta.valueOf(command, params);
            caches.putIfAbsent(method, info);
        }

        Command command = info.getCommand();
        Map<Integer, ParamMeta> params = info.getParams();
        Object body;
        if (args == null) {
            body = null;
        } else {
            int length = args.length;
            if (length == 0) {
                body = null;
            } else if (length == 1) {
                ParamMeta paramMeta = params.get(0);
                if (paramMeta == null || paramMeta.name == null) {
                    body = args[0];
                } else {
                    Map<String, Object> p = new HashMap<>();
                    for (int i = 0; i < args.length; i++) {
                        p.put(paramMeta.getName(), args[i]);
                    }
                    body = p;
                }
            } else {
                Map<String, Object> p = new HashMap<>();
                Object inrequest = null;
                for (int i = 0; i < args.length; i++) {
                    ParamMeta paramMeta = params.get(i);
                    if (paramMeta == null) {
                        continue;
                    }
                    if (paramMeta.getName() == null) {
                        inrequest = args[i];
                        break;
                    }
                    p.put(paramMeta.getName(), args[i]);
                }
                if (inrequest == null) {
                    body = p;
                } else {
                    body = inrequest;
                }
            }
        }
        Request<Object> request = Request.valueOf(command, body);
        return request;
    }

    protected static class InvokerMeta {
        private Command command;
        private Map<Integer, ParamMeta> params;

        public Command getCommand() {
            return command;
        }

        public Map<Integer, ParamMeta> getParams() {
            return params;
        }

        static InvokerMeta valueOf(Command command, Map<Integer, ParamMeta> params) {
            InvokerMeta e = new InvokerMeta();
            e.command = command;
            e.params = params;
            return e;
        }
    }

    protected static class ParamMeta {
        private String name;
        private Class<?> type;

        public String getName() {
            return name;
        }

        public Class<?> getType() {
            return type;
        }

        static ParamMeta valueOf(String name, Class<?> type) {
            ParamMeta e = new ParamMeta();
            e.name = name;
            e.type = type;
            return e;
        }
    }

}